﻿using System;
using System.Windows.Input;
using System.Windows.Threading;

namespace WinwiseARDrone.TabletClient.Core
{
    /// <summary>
    /// Représente une commande.
    /// </summary>
    /// <typeparam name="TParam">Type du paramètre.</typeparam>
    public class CommandViewModel<TParam> : ICommand
    {
        #region Evénements

        /// <summary>
        /// Se produit lorsque la valeur de <c>CanExecute</c> peut avoir changée.
        /// </summary>
        public event EventHandler CanExecuteChanged;

        #endregion

        #region Constructeurs

        /// <summary>
        /// Initialise une nouvelle instance de <c>CommandViewModel&lt;TParam&gt;</c>.
        /// </summary>
        public CommandViewModel()
        {
        }

        #endregion

        #region Propriétés

        /// <summary>
        /// Obtient ou définit l'action d'exécution.
        /// </summary>
        public Func<object, TParam> AdaptParameterFunction
        {
            get;
            set;
        }

        /// <summary>
        /// Obtient ou définit une valeur indiquant si <c>InvalidateCanExecute</c> doit être appelé après invoquation de la commande.
        /// </summary>
        public bool AutoInvalidateCanExecute
        {
            get;
            set;
        }

        /// <summary>
        /// Obtient ou définit la fonction validant l'exécution.
        /// </summary>
        public Func<TParam, bool> CanExecuteFunction
        {
            get;
            set;
        }

        /// <summary>
        /// Obtient ou définit l'action d'exécution.
        /// </summary>
        public Action<TParam> ExecuteAction
        {
            get;
            set;
        }

        /// <summary>
        /// Obtient ou définit une valeur indiquant si <c>InvalidateCanExecute</c> doit être appelé lorsque le paramètre est <c>null</c>.
        /// Cela permet de corriger le comportement de WPF qui commence par appeler <c>CanExecute</c> avant de définir le paramètre sur certains contrôles.
        /// </summary>
        public bool InvalidateCanExecuteOnNullParameter
        {
            get;
            set;
        }

        #endregion

        #region Fonctions publiques

        /// <summary>
        /// Obtient une valeur indiquant si la commande peut être invoquée.
        /// </summary>
        /// <param name="p_sParameter">Paramètre de la commande.</param>
        /// <returns>Valeur indiquant si la commande peut être invoquée.</returns>
        public bool CanExecute(object p_sParameter)
        {
            if (p_sParameter == null && this.InvalidateCanExecuteOnNullParameter)
            {
                Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => this.InvalidateCanExecute()));
                return (false);
            }
            else
            {
                if (this.CanExecuteFunction == null)
                    return (true);

                return (this.CanExecuteFunction(this.AdaptParameter(p_sParameter)));
            }
        }

        /// <summary>
        /// Invoque la commande avec le paramètre spécifié.
        /// </summary>
        /// <param name="p_sParameter">Paramètre de la commande.</param>
        public void Execute(object p_sParameter)
        {
            if (this.ExecuteAction != null)
            {
                this.ExecuteAction(this.AdaptParameter(p_sParameter));

                if (this.AutoInvalidateCanExecute)
                    this.InvalidateCanExecute();
            }
        }

        /// <summary>
        /// Invalide le résultat de <c>CanExecute</c>.
        /// </summary>
        public void InvalidateCanExecute()
        {
            this.OnCanExecuteChanged(EventArgs.Empty);
        }

        #endregion

        #region Fonctions protégées

        /// <summary>
        /// Adapte le paramètre au type de destination.
        /// </summary>
        /// <param name="p_sParameter">Paramètre à adapter.</param>
        /// <returns>Paramètre adapté.</returns>
        protected virtual TParam AdaptParameter(object p_sParameter)
        {
            if (this.AdaptParameterFunction != null)
            {
                return (this.AdaptParameterFunction(p_sParameter));
            }
            else
            {
                if (p_sParameter is TParam)
                    return ((TParam)p_sParameter);
                else
                    return ((TParam)Convert.ChangeType(p_sParameter, typeof(TParam)));
            }
        }

        /// <summary>
        /// Déclenche l'événement <c>CanExecuteChanged</c>.
        /// </summary>
        /// <param name="e">Données de l'événement.</param>
        protected virtual void OnCanExecuteChanged(EventArgs e)
        {
            if (this.CanExecuteChanged != null)
                this.CanExecuteChanged(this, e);
        }

        #endregion
    }

    /// <summary>
    /// Représente une commande sans paramètre.
    /// </summary>
    public class CommandViewModel : CommandViewModel<object>
    {
        #region Constructeurs

        /// <summary>
        /// Initialise une nouvelle instance de <c>CommandViewModel</c>.
        /// </summary>
        public CommandViewModel()
        {
        }

        #endregion
    }
}
